/* ================================================================================
<copyright file="ManagedPerformanceCounter.cs" company="The Framework Factory, LLC" lastWriteTime="12/05/2012 23:23:47 PM">
  Copyright (c) 2012 The Framework Factory, LLC.  All rights reserved.
</copyright>
=================================================================================== */
using System;
using System.ComponentModel;
using System.Diagnostics;

namespace FWF.Logging.Statistics
{
    /// <summary>
    /// 
    /// </summary>
    public class ManagedPerformanceCounter : IDisposable
    {
        private PerformanceCounter _counter;
        private StatCounter _statCounter;

        /// <summary>
        /// 
        /// </summary>
        public static TimeSpan DefaultSnapShotExpiration = TimeSpan.FromSeconds( 2 );

        private static readonly IFWFLog _log = LogFactory.Get( typeof ( ManagedPerformanceCounter ) );
        private static readonly object _lockObject = new object();

        #region Constructors / Finalizer

        /// <summary>
        /// Initializes a new instance of the <see cref="ManagedPerformanceCounter"/> class.
        /// </summary>
        protected ManagedPerformanceCounter()
        {
        }

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            Dispose( true );
            GC.SuppressFinalize( this );
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="disposing">if set to <c>true</c> [disposing].</param>
        protected void Dispose( bool disposing )
        {
            if ( disposing )
            {
                if ( _counter != null )
                {
                    _counter.Close();
                }
            }
        }

        #endregion

        #region Static Performance Counter Creation Methods

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public static void Create(
            string categoryName,
            string categoryHelp,
            string counterName,
            string counterHelp,
            PerformanceCounterType counterType
            )
        {
            lock ( _lockObject )
            {
                //
                PerformanceCounterCategory perfCategory = null;

                if ( PerformanceCounterCategory.Exists( categoryName ) )
                {
                    perfCategory = new PerformanceCounterCategory( categoryName, "." );
                }

                // Create counter if needed
                if ( !PerformanceCounterCategory.Exists( categoryName ) ||
                    !PerformanceCounterCategory.CounterExists( counterName, categoryName ) )
                {
                    var col = new CounterCreationDataCollection();

                    // Create collection based upon existing category
                    if ( perfCategory != null )
                    {
                        // Determine if category has instance name
                        foreach ( var performanceCounter in perfCategory.GetCounters() )
                        {
                            var data = new CounterCreationData();
                            data.CounterName = performanceCounter.CounterName;
                            data.CounterHelp = performanceCounter.CounterHelp;
                            data.CounterType = performanceCounter.CounterType;

                            col.Add( data );
                        }
                    }

                    // Add current instance counter
                    CounterCreationData d = new CounterCreationData();
                    d.CounterName = counterName;
                    d.CounterHelp = counterHelp;
                    d.CounterType = counterType;

                    col.Add( d );

                    // Create
                    try
                    {
                        if ( PerformanceCounterCategory.Exists( categoryName ) )
                        {
                            PerformanceCounterCategory.Delete( categoryName );
                        }

                        PerformanceCounterCategory.Create(
                            categoryName,
                            categoryHelp,
                            PerformanceCounterCategoryType.SingleInstance,
                            col
                            );
                    }
                    catch ( Win32Exception ex )
                    {
                        _log.Error( "Unable to create performace counter: " + ex.Message, ex );
                    }
                }

            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="categoryName"></param>
        /// <param name="counterName"></param>
        public static void Remove( string categoryName, string counterName )
        {
            lock ( _lockObject )
            {
                if ( !PerformanceCounterCategory.Exists( categoryName ) )
                {
                    return;
                }


                if ( string.IsNullOrEmpty( counterName ) )
                {
                    // Remove entire category
                    PerformanceCounterCategory.Delete( categoryName );
                }
                else
                {
                    // If specific counter does not exist, no work to do
                    if ( !PerformanceCounterCategory.CounterExists( counterName, categoryName ) )
                    {
                        return;
                    }

                    var perfCategory = new PerformanceCounterCategory( categoryName, "." );

                    var col = new CounterCreationDataCollection();

                    // Determine if category has instance name
                    foreach ( var performanceCounter in perfCategory.GetCounters() )
                    {
                        // Add all counters in the category, except for the one being removed
                        if ( !performanceCounter.CounterName.Equals( counterName ) )
                        {
                            var data = new CounterCreationData();
                            data.CounterName = performanceCounter.CounterName;
                            data.CounterHelp = performanceCounter.CounterHelp;
                            data.CounterType = performanceCounter.CounterType;

                            col.Add( data );
                        }    
                    }

                    // Create
                    try
                    {
                        if ( PerformanceCounterCategory.Exists( categoryName ) )
                        {
                            PerformanceCounterCategory.Delete( categoryName );
                        }

                        if ( col.Count > 0 )
                        {
                            PerformanceCounterCategory.Create(
                                perfCategory.CategoryName,
                                perfCategory.CategoryHelp,
                                PerformanceCounterCategoryType.SingleInstance,
                                col
                                );
                        }
                    }
                    catch ( Win32Exception ex )
                    {
                        _log.Error( "Unable to create performance counter: " + ex.Message, ex );
                    }
                }
            }

        }

        #endregion

        /// <summary>
        /// Refers the specified category name.
        /// </summary>
        /// <param name="log">The log.</param>
        /// <param name="categoryName">Name of the category.</param>
        /// <param name="counterName">Name of the counter.</param>
        /// <returns></returns>
        public static ManagedPerformanceCounter Refer( IFWFLog log, string categoryName, string counterName )
        {
            return Refer(
                log,
                categoryName,
                counterName,
                string.Empty
                );
        }

        /// <summary>
        /// Refers the specified category name.
        /// </summary>
        /// <param name="log">The log.</param>
        /// <param name="categoryName">Name of the category.</param>
        /// <param name="counterName">Name of the counter.</param>
        /// <param name="instanceName">Name of the instance.</param>
        /// <returns></returns>
        public static ManagedPerformanceCounter Refer( IFWFLog log, string categoryName, string counterName, string instanceName )
        {
            return Refer(
                log,
                categoryName,
                counterName,
                instanceName,
                DefaultSnapShotExpiration
                );
        }

        /// <summary>
        /// Refers the specified category name.
        /// </summary>
        /// <param name="log">The log.</param>
        /// <param name="categoryName">Name of the category.</param>
        /// <param name="counterName">Name of the counter.</param>
        /// <param name="instanceName">Name of the instance.</param>
        /// <param name="snapShotExpiration">The snap shot expiration.</param>
        /// <returns></returns>
        /// <exception cref="System.ArgumentNullException">categoryName</exception>
        public static ManagedPerformanceCounter Refer(
            IFWFLog log,
            string categoryName,
            string counterName,
            string instanceName,
            TimeSpan snapShotExpiration 
            )
        {
            if ( string.IsNullOrEmpty( categoryName ) )
            {
                throw new ArgumentNullException( "categoryName" );
            }
            if ( string.IsNullOrEmpty( counterName ) )
            {
                throw new ArgumentNullException( "counterName" );
            }

            var perfCounter = new ManagedPerformanceCounter();
            bool bHasError = false;
            Exception exception = null;

            try
            {
                // Create instance of performance counter
                perfCounter._counter = new PerformanceCounter( categoryName, counterName );
                perfCounter._counter.MachineName = ".";
                perfCounter._counter.InstanceName = instanceName;
                perfCounter._counter.ReadOnly = true;

                // Do not need for these to be set, since the performance counter has already been created
                //perfCounter._counter.InstanceLifetime = PerformanceCounterInstanceLifetime.Global;
                //perfCounter._counter.CounterType = counterType;

                // Create a StatCounter to record all performance counter measurements to the log
                perfCounter._statCounter =
                    StatCounter.Create( log, categoryName, counterName, snapShotExpiration, instanceName );

                // Provide an fetch method so that the current performance 
                // counter value is retrieve, just before logging
                perfCounter._statCounter.FetchValueMethod =
                    () => Convert.ToInt64( perfCounter._counter.NextValue() );
            }
            catch ( InvalidOperationException ex )
            {
                bHasError = true;
                exception = ex;
            }
            catch ( Win32Exception ex )
            {
                bHasError = true;
                exception = ex;
            }
            catch ( PlatformNotSupportedException ex )
            {
                bHasError = true;
                exception = ex;
            }
            catch ( UnauthorizedAccessException ex )
            {
                bHasError = true;
                exception = ex;
            }

            if ( bHasError )
            {
                _log.Error( "Unable to locate performance counter " + categoryName + ":" + counterName, exception );

                return null;
            }

            return perfCounter;
        }

        /// <summary>
        /// Gets or sets the name of the counter.
        /// </summary>
        /// <value>The name of the counter.</value>
        public string Name
        {
            get
            {
                return _counter.CounterName;
            }
        }

        /// <summary>
        /// Gets or sets the name of the category.
        /// </summary>
        /// <value>The name of the category.</value>
        public string CategoryName
        {
            get
            {
                return _counter.CategoryName;
            }
        }

        /// <summary>
        /// Increments this instance.
        /// </summary>
        /// <returns></returns>
        public long Increment()
        {
            long returnValue = -1;

            try
            {
                returnValue = _statCounter.Increment();
                returnValue = _counter.Increment();
            }
            catch ( Exception ex )
            {
                _log.Error( ex.Message, ex );
            }

            return returnValue;
        }

        /// <summary>
        /// Increments the by.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public long IncrementBy( long value )
        {
            long returnValue = -1;

            try
            {
                returnValue = _statCounter.IncrementBy( value );
                returnValue = _counter.IncrementBy( value );
            }
            catch ( Exception ex )
            {
                _log.Error( ex.Message, ex );
            }

            return returnValue;
        }

        /// <summary>
        /// Decrements this instance.
        /// </summary>
        /// <returns></returns>
        public long Decrement()
        {
            long returnValue = -1;

            try
            {
                returnValue = _statCounter.Decrement();
                returnValue = _counter.Decrement();
            }
            catch ( Exception ex )
            {
                _log.Error( ex.Message, ex );
            }

            return returnValue;
        }

    }
}
